package main

import (
	"context"
	"fmt"
	"server/cs"
	"sync"

	"github.com/davecgh/go-spew/spew"
	"go.uber.org/atomic"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type server struct {
	cs.UnimplementedGameServer

	// 必须协程全安
	generateID  atomic.Uint32 // 用于ID生成
	UUID2Player sync.Map      // map[string]*player
}

var (
	updateResp = &cs.UpdateResponse{}
)

func newServer() *server {
	return &server{
		UUID2Player: sync.Map{},
	}
}

func (s *server) EnterRoom(ctx context.Context, req *cs.EnterRoomRequest) (*cs.EnterRoomResponse, error) {
	playerUUID, ok := getPlayerUUID(ctx)
	if !ok {
		return nil, status.Errorf(codes.InvalidArgument, "no player-uuid")
	}
	playerID := s.generateID.Inc()
	s.UUID2Player.Store(playerUUID, newPlayer(playerID))
	resp := &cs.EnterRoomResponse{
		PlayerId: playerID,
	}
	fmt.Printf("enter room, resp=%s, UUID=%s\n", spew.Sdump(resp), playerUUID)
	return resp, nil
}

func (s *server) Update(ctx context.Context, req *cs.UpdateRequest) (*cs.UpdateResponse, error) {
	plyr := s.getPlayer(ctx)
	if plyr == nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid player")
	}

	// 广播到其他玩家
	s.broadcastUpdate(plyr, req.GetTransform())

	return updateResp, nil
}

func (s *server) ReceiveUpdate(_ *cs.ReceiveUpdateRequest, stream cs.Game_ReceiveUpdateServer) error {
	plyr := s.getPlayer(stream.Context())
	if plyr == nil {
		return status.Errorf(codes.InvalidArgument, "invalid player")
	}
	return plyr.RunReceiveUpdate(stream)
}

// getPlayer 从metadata读取UUID, 并查找对应的player
func (s *server) getPlayer(ctx context.Context) *player {
	playerUUID, ok := getPlayerUUID(ctx)
	if !ok {
		return nil
	}

	p, ok := s.UUID2Player.Load(playerUUID)
	if !ok {
		return nil
	}
	return p.(*player)
}

func (s *server) broadcastUpdate(plyr *player, tr *cs.Transform) {
	playerID := plyr.GetPlayerID()
	msg := &cs.UpdateMessage{
		PlayerId:  playerID,
		Transform: tr,
	}
	s.UUID2Player.Range(func(_, value interface{}) bool {
		valuePlayer := value.(*player)
		if valuePlayer != plyr {
			valuePlayer.PushUpdateMessage(msg)
		}
		return true
	})
}
